// httpListener
// Http端口监听单元
package main

import (
	"crypto/md5"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const (
	//MaxPhotoSize 图片最大不能超过2M
	MaxPhotoSize int = 2 * 1024 * 1024
)

// HttpListener Http监听类
type HttpListener struct {
	dbo *DBOperation
	cfg *Config
	tr  *http.Transport
}

// ListenHttpService 监听函数
func ListenHttpService(cfg *Config, db *DBOperation) {
	listenter := HttpListener{}
	listenter.dbo = db
	listenter.cfg = cfg
	listenter.tr = &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}

	listenter.listen()
}

func (p *HttpListener) listen() {
	sPort := ""
	var err error

	http.HandleFunc("/photo", p.Photo)
	http.HandleFunc("/", p.Upload)

	//开始监听
	go func() {
		sPort = fmt.Sprintf(":%d", p.cfg.HttpsPort)
		zLog("开始监听端口:", sPort, " HTTPS:", p.cfg.HTTPS)
		if p.cfg.HTTPS {
			err = http.ListenAndServeTLS(sPort, p.cfg.CertFile, p.cfg.KeyFile, nil)
		} else {
			err = http.ListenAndServe(sPort, nil)
		}
		if err != nil {
			zLog("监听端口", sPort, "失败:", err)
		}
	}()
}

// Photo 照片上传
func (p *HttpListener) Photo(w http.ResponseWriter, req *http.Request) {
	zLog("Photo start......")
	if userid, ok := p.commonCheck(&w, req, MaxPhotoSize); ok {
		//req.ParseMultipartForm()
		file, fileHeader, err := req.FormFile("uploadfile")
		if err != nil {
			ret := make(map[string]interface{})
			ret["errcode"] = -1
			ret["msg"] = err.Error()
			buf, _ := json.Marshal(ret)
			w.Write(buf)
			return
		}
		fileType := fileHeader.Header.Get("Content-Type")
		if err == nil {
			var url, fullFileName string
			url, fullFileName, err = p.saveToFile(file, fileHeader.Filename, "photo")
			if err == nil {
				zLog(userid, "上传了", fileHeader.Filename, ",类型:", fileType, ",大小:", fileHeader.Size, ",保存为:", fullFileName)

				res := fmt.Sprintf(`"url":"%s"`, url)
				res = p.makeResultStr(0, res)
				w.Write([]byte(res))
				return
			}
		}
		if err != nil {
			zLog(userid, "上传了", fileHeader.Filename, ",类型:", fileType, ",大小:", fileHeader.Size, ",出现异常:", err.Error())
			res := p.makeResultStr(-1, err.Error())
			w.Write([]byte(res))
		}
	}
}

// Upload 通用文件上传
func (p *HttpListener) Upload(w http.ResponseWriter, req *http.Request) {
	if userid, ok := p.commonCheck(&w, req, MaxPhotoSize); ok {
		file, fileHeader, err := req.FormFile("file")
		if err != nil {
			ret := make(map[string]interface{})
			ret["errcode"] = -1
			ret["msg"] = err.Error()
			buf, _ := json.Marshal(ret)
			w.Write(buf)
			return
		}
		sErr := ""
		category := req.FormValue("category")
		if category == "" {
			sErr = p.makeResultStr(-1, "缺少必要的参数")
			w.Write([]byte(sErr))
			return
		}
		//判断是否为合法的category
		//if strings.Index(p.cfg.Category, ","+category+",") < 0 {
		if !strings.Contains(p.cfg.Category, ","+category+",") {
			sErr = p.makeResultStr(-1, "无效的category参数")
			w.Write([]byte(sErr))
			return
		}

		fileType := fileHeader.Header.Get("Content-Type")
		if err == nil {
			var url, fullFileName string
			url, fullFileName, err = p.saveToFile(file, fileHeader.Filename, category)
			if err == nil {
				zLog(userid, "上传了", fileHeader.Filename, ",类型:", fileType, ",大小:", fileHeader.Size, ",保存为:", fullFileName)

				res := fmt.Sprintf(`"url":"%s"`, url)
				res = p.makeResultStr(0, res)
				w.Write([]byte(res))
				return
			}
		}
		if err != nil {
			zLog(userid, "上传了", fileHeader.Filename, ",类型:", fileType, ",大小:", fileHeader.Size, ",出现异常:", err.Error())
			res := p.makeResultStr(-1, err.Error())
			w.Write([]byte(res))
		}
	}
}

// 保存文件
// 参数：
//
//	file:文件内容
//	fileName: 上传的文件名
//	path: 保存文件的目录
//
// 返回参数：
//
//		url:文件的链接
//	 fullFileName:保存文件的全路径
//		err:如果失败，返回错误
func (p *HttpListener) saveToFile(file multipart.File, fileName, savePath string) (url string, fullFileName string, err error) {
	url = ""
	err = nil
	var buf []byte

	fullPath := p.cfg.Path + savePath + "/"
	err = os.MkdirAll(fullPath, 0755)
	if err == nil {
		fileExt := path.Ext(fileName)
		fileNewName := p.md5FileName(fileName) + fileExt
		fullFileName = fullPath + fileNewName
		buf, err = ioutil.ReadAll(file)
		if err == nil {
			err = ioutil.WriteFile(fullFileName, buf, 0755)
			if err == nil {
				url = p.cfg.PreUrl + savePath + "/" + fileNewName
			}
		}
	}

	return
}

// 将文件名做MD5处理
func (p *HttpListener) md5FileName(fileName string) string {
	t := time.Now().UnixNano()
	fileName = fmt.Sprintf("%s%d", fileName, t)
	data := []byte(fileName)
	has := md5.Sum(data)
	newFileName := fmt.Sprintf("%x", has)
	return newFileName
}

// 通用检查
// maxSize == 0，表示不检查文件大小
func (p *HttpListener) commonCheck(w *http.ResponseWriter, req *http.Request, maxSize int) (userid string, result bool) {
	p.logRequest(req)

	(*w).Header().Add("Access-Control-Allow-Origin", "*")
	(*w).Header().Set("Access-Control-Allow-Credentials", "true")
	(*w).Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
	(*w).Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")

	// 处理预检
	if req.Method == "OPTIONS" {
		(*w).WriteHeader(http.StatusNoContent)
		return "", false
	}

	query := req.URL.Query()
	var sErr string

	//检查上传文件的大小
	if maxSize > 0 {
		contentLength := req.Header.Get("Content-Length")
		fileSize, _ := strconv.Atoi(contentLength)
		if fileSize > maxSize {
			sErr = p.makeResultStr(1028, "文件超出指定大小")
			(*w).Write([]byte(sErr))
			result = false
			return
		}
	}

	//检查userid/accesstoken
	userid = query.Get("userid")
	accesstoken := query.Get("accesstoken")

	if userid == "" && accesstoken == "" {
		sErr = p.makeResultStr(1003, "缺少必要的字段")
		(*w).Write([]byte(sErr))
		result = false
		return
	}

	if req.Method != "POST" {
		sErr = p.makeResultStr(1002, "无效的访问方式")
		(*w).Write([]byte(sErr))
		result = false
		return
	}

	//检查accesstoken是否正确
	if !p.checckAccessToken(userid, accesstoken) {
		sErr = p.makeResultStr(1006, "非法访问")
		(*w).Write([]byte(sErr))
		result = false
		return
	}

	result = true
	return
}

// 记录日志
type RequestInfoT struct {
	Host   string
	Header http.Header
	Path   string
	Method string
	Source string // 请求来源
	//RemoteIP string //请求的IP
	Query string //请求内容
}

func (p HttpListener) logRequest(req *http.Request) {
	lr := RequestInfoT{
		Source: req.URL.Query().Get("source"),
		Host:   req.Host,
		Path:   req.URL.Path,
		Query:  req.URL.RequestURI(),
		Method: req.Method,
		Header: req.Header,
	}

	buf, _ := json.Marshal(lr)
	zLog("[ACCESS]", string(buf))
}

func (p *HttpListener) makeResultStr(code int, msg string) string {
	res := ""

	if code == 0 {
		if msg == "" {
			res = `{"errcode":0}`
		} else {
			res = `{"errcode":0,` + msg + `}`
		}
	} else {
		res = fmt.Sprintf(`{"errcode":%d,"msg":"%s"}`, code, msg)
	}

	return res
}

func (p *HttpListener) checckAccessToken(userid, token string) bool {
	type Result struct {
		ErrCode int    `json:"errcode"` //`json: "errcode"`
		Msg     string `json:"msg"`
	}

	sUrl := fmt.Sprintf(p.cfg.CheckAccessUrl+"checkaccess?userid=%s&accesstoken=%s", userid, token)
	c := http.Client{
		Transport: p.tr,
	}
	resp, err := c.Get(sUrl)
	if err != nil {
		return false
	}

	defer resp.Body.Close()
	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return false
	}

	var res Result
	err = json.Unmarshal(buf, &res)
	if err != nil {
		return false
	}

	return res.ErrCode == 0
}
